/*
 * NPlot - A charting library for .NET
 * 
 * LegendBase.cs
 * Copyright (C) 2003-2006 Matt Howlett and others.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System.Collections;
using System.Drawing;

namespace NPlot
{
    /// <summary>
    /// Provides functionality for drawing legends.
    /// </summary>
    /// <remarks>
    /// The class is quite closely tied to PlotSurface2D.
    /// </remarks>
    public class LegendBase
    {
        /// <summary>
        /// The types of legend borders (enum).
        /// </summary>
        public enum BorderType
        {
            /// <summary>
            /// No border.
            /// </summary>
            None = 0,

            /// <summary>
            /// Line border.
            /// </summary>
            Line = 1,

            /// <summary>
            /// Shaded border.
            /// </summary>
            Shadow = 2
            //Curved = 3
        }

        private Color bgColor_;
        private Color borderColor_;
        private Font font_;
        private int numberItemsHorizontally_ = 1;
        private int numberItemsVertically_ = -1;
        private Color textColor_;

        /// <summary>
        /// Constructor.
        /// </summary>
        public LegendBase()
        {
            Font = new Font(new FontFamily("Arial"), 10, FontStyle.Regular, GraphicsUnit.Pixel);
            BackgroundColor = Color.White;
            BorderColor = Color.Black;
            TextColor = Color.Black;
            BorderStyle = BorderType.Shadow;
            AutoScaleText = false;
        }

        /// <summary>
        /// The font used to draw text in the legend.
        /// </summary>
        public Font Font
        {
            get { return font_; }
            set { font_ = value; }
        }

        /// <summary>
        /// The color used to draw text in the legend.
        /// </summary>
        public Color TextColor
        {
            get { return textColor_; }
            set { textColor_ = value; }
        }

        /// <summary>
        /// The background color of the legend.
        /// </summary>
        public Color BackgroundColor
        {
            get { return bgColor_; }
            set { bgColor_ = value; }
        }

        /// <summary>
        /// The color of the legend border.
        /// </summary>
        public Color BorderColor
        {
            get { return borderColor_; }
            set { borderColor_ = value; }
        }

        /// <summary>
        /// The border style to use for the legend.
        /// </summary>
        public BorderType BorderStyle { get; set; }

        /// <summary>
        /// Whether or not to auto scale text in the legend according the physical
        /// dimensions of the plot surface.
        /// </summary>
        public bool AutoScaleText { get; set; }

        /// <summary>
        /// Setting this does two things. First of all, it sets the maximum number of
        /// items in the legend vertically. Second of all, it makes the legend grow
        /// horizontally (as it must given this constraint).
        /// </summary>
        public int NumberItemsVertically
        {
            set
            {
                numberItemsVertically_ = value;
                numberItemsHorizontally_ = -1;
            }
        }

        /// <summary>
        /// Setting this does two things. First of all, it sets the maximum number of
        /// items in the legend horizontally. Second of all, it makes the legend grow
        /// vertically (as it must given this constraint).
        /// </summary>
        public int NumberItemsHorizontally
        {
            set
            {
                numberItemsHorizontally_ = value;
                numberItemsVertically_ = -1;
            }
        }

        /// <summary>
        /// Get the bounding box of the rectangle.
        /// </summary>
        /// <param name="position">the position of the top left of the legend.</param>
        /// <param name="plots">Array of plot objects to appear in the legend.</param>
        /// <param name="scale">if the legend is set to scale, the amount to scale by.</param>
        /// >
        /// <returns></returns>
        /// <remarks>do implementation that doesn't call draw. Change xPos, yPos to PointF</remarks>
        public Rectangle GetBoundingBox(Point position, ArrayList plots, float scale)
        {
            System.Drawing.Bitmap b = new System.Drawing.Bitmap(1, 1);
            Graphics g = Graphics.FromImage(b);
            return Draw(g, position, plots, scale);
        }

        /// <summary>
        /// Draw The legend
        /// </summary>
        /// <param name="g">The graphics surface on which to draw</param>
        /// <param name="position">The position of the top left of the axis.</param>
        /// <param name="plots">Array of plot objects to appear in the legend.</param>
        /// <param name="scale">if the legend is set to scale, the amount to scale by.</param>
        /// <returns>bounding box</returns>
        public Rectangle Draw(Graphics g, Point position, ArrayList plots, float scale)
        {
            // first of all determine the Font to use in the legend.
            Font textFont;
            if (AutoScaleText)
            {
                textFont = Utils.ScaleFont(font_, scale);
            }
            else
            {
                textFont = font_;
            }

            // determine max width and max height of label strings and
            // count the labels. 
            int labelCount = 0;
            int maxHt = 0;
            int maxWd = 0;
            int unnamedCount = 0;
            for (int i = 0; i < plots.Count; ++i)
            {
                if (!(plots[i] is IPlot))
                {
                    continue;
                }

                IPlot p = (IPlot) plots[i];

                if (!p.ShowInLegend)
                {
                    continue;
                }
                string label = p.Label;
                if (label == "")
                {
                    unnamedCount += 1;
                    label = "Series " + unnamedCount.ToString();
                }
                SizeF labelSize = g.MeasureString(label, textFont);
                if (labelSize.Height > maxHt)
                {
                    maxHt = (int) labelSize.Height;
                }
                if (labelSize.Width > maxWd)
                {
                    maxWd = (int) labelSize.Width;
                }

                ++labelCount;
            }

            bool extendingHorizontally = numberItemsHorizontally_ == -1;
            bool extendingVertically = numberItemsVertically_ == -1;

            // determine width in legend items count units.
            int widthInItemCount = 0;
            if (extendingVertically)
            {
                if (labelCount >= numberItemsHorizontally_)
                {
                    widthInItemCount = numberItemsHorizontally_;
                }
                else
                {
                    widthInItemCount = labelCount;
                }
            }
            else if (extendingHorizontally)
            {
                widthInItemCount = labelCount/numberItemsVertically_;
                if (labelCount%numberItemsVertically_ != 0)
                    widthInItemCount += 1;
            }
            else
            {
                throw new NPlotException("logic error in legend base");
            }

            // determine height of legend in items count units.
            int heightInItemCount = 0;
            if (extendingHorizontally)
            {
                if (labelCount >= numberItemsVertically_)
                {
                    heightInItemCount = numberItemsVertically_;
                }
                else
                {
                    heightInItemCount = labelCount;
                }
            }
            else // extendingVertically
            {
                heightInItemCount = labelCount/numberItemsHorizontally_;
                if (labelCount%numberItemsHorizontally_ != 0)
                    heightInItemCount += 1;
            }

            int lineLength = 20;
            int hSpacing = (int) (5.0f*scale);
            int vSpacing = (int) (3.0f*scale);
            int boxWidth = (int) (widthInItemCount*(lineLength + maxWd + hSpacing*2.0f) + hSpacing);
            int boxHeight = (int) ((float) heightInItemCount*(maxHt + vSpacing) + vSpacing);

            int totalWidth = boxWidth;
            int totalHeight = boxHeight;

            // draw box around the legend.

            if (BorderStyle == BorderType.Line)
            {
                g.FillRectangle(new SolidBrush(bgColor_), position.X, position.Y, boxWidth, boxHeight);
                g.DrawRectangle(new Pen(borderColor_), position.X, position.Y, boxWidth, boxHeight);
            }
            else if (BorderStyle == BorderType.Shadow)
            {
                int offset = (int) (4.0f*scale);
                g.FillRectangle(new SolidBrush(Color.FromArgb(128, Color.Gray)), position.X + offset, position.Y + offset, boxWidth, boxHeight);
                g.FillRectangle(new SolidBrush(bgColor_), position.X, position.Y, boxWidth, boxHeight);
                g.DrawRectangle(new Pen(borderColor_), position.X, position.Y, boxWidth, boxHeight);

                totalWidth += offset;
                totalHeight += offset;
            }

                /*
			   else if ( this.BorderStyle == BorderType.Curved )
			   {
				   // TODO. make this nice.
			   }
			*/

            else
            {
                // do nothing.
            }

            // now draw entries in box..
            labelCount = 0;
            unnamedCount = 0;

            int plotCount = -1;
            for (int i = 0; i < plots.Count; ++i)
            {
                if (!(plots[i] is IPlot))
                {
                    continue;
                }

                IPlot p = (IPlot) plots[i];

                if (!p.ShowInLegend)
                {
                    continue;
                }

                plotCount += 1;

                int xpos, ypos;
                if (extendingVertically)
                {
                    xpos = plotCount%numberItemsHorizontally_;
                    ypos = plotCount/numberItemsHorizontally_;
                }
                else
                {
                    xpos = plotCount/numberItemsVertically_;
                    ypos = plotCount%numberItemsVertically_;
                }

                int lineXPos = (int) (position.X + hSpacing + xpos*(lineLength + maxWd + hSpacing*2.0f));
                int lineYPos = (position.Y + vSpacing + ypos*(vSpacing + maxHt));
                p.DrawInLegend(g, new Rectangle(lineXPos, lineYPos, lineLength, maxHt));

                int textXPos = lineXPos + hSpacing + lineLength;
                int textYPos = lineYPos;
                string label = p.Label;
                if (label == "")
                {
                    unnamedCount += 1;
                    label = "Series " + unnamedCount.ToString();
                }

                g.DrawString(label, textFont,
                             new SolidBrush(textColor_), textXPos, textYPos);

                ++labelCount;
            }
            return new Rectangle(position.X, position.Y, totalWidth, totalHeight);
        }
    }
}